#include "Win32Application.h"


Win32Application::Win32Application()
{
	m_hwnd = NULL;
	m_memoryDC = NULL;
	m_memoryBmp = NULL;
}


Win32Application::~Win32Application()
{
}


LRESULT Win32Application::Create(int width, int height, const TCHAR* title)
{
	if (!CreateWin32Application(width, height, title)) {
		return E_FAIL;
	}

	HRESULT hres = OnCreate();
	if (FAILED(hres)) {
		Release();
		return hres;
	}

	return S_OK;
}


void Win32Application::Release()
{
	if (m_hwnd) {
		OnRelease();

		DisposeBackbuffer();

		HWND hwnd = m_hwnd;
		m_hwnd = NULL;

		::DestroyWindow(hwnd);
	}
}


void Win32Application::Close()
{
	if (m_hwnd) {
		::PostMessage(m_hwnd, WM_CLOSE, 0, 0);
	}
}


int Win32Application::Run()
{
	MSG msg = {0};

	while (msg.message != WM_QUIT) {
		if (::GetMessage(&msg, m_hwnd, 0, 0)) {
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
	}

	// Release GDI+
	//
	GdiplusShutdown(m_gdip);

	return (int) msg.wParam;
}


void Win32Application::Refresh()
{
	RECT rect;
	::GetClientRect(m_hwnd, &rect);
	::InvalidateRect(m_hwnd, &rect, FALSE);
}


BOOL Win32Application::CreateWin32Application(int width, int height, const TCHAR* title)
{
	HINSTANCE	hinstance = ::GetModuleHandle(NULL);
	DWORD		style = WS_OVERLAPPEDWINDOW;
	HMENU		menu = 0;

	// Add borders to the client width and height
	//
	width += ::GetSystemMetrics(SM_CXSIZEFRAME * 2);
	height += ::GetSystemMetrics(SM_CYSIZEFRAME * 2) + ::GetSystemMetrics(SM_CYMENU);

	// Initialize GDI+
	//
	GdiplusStartupInput gdiplusStartupInput;
	if (GdiplusStartup(&m_gdip, &gdiplusStartupInput, NULL) != Ok) {
		return FALSE;
	}

	// Register window class
	//
	TCHAR windowClassName[] = _T("Win32Application");

	WNDCLASSEX wcex;
	wcex.cbSize			= sizeof(WNDCLASSEX);
	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hinstance;
	wcex.hIcon			= LoadIcon(hinstance, NULL);
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= NULL;
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= windowClassName;
	wcex.hIconSm		= NULL;
	RegisterClassEx(&wcex);

	// Create window
	//
	m_hwnd = ::CreateWindow(windowClassName, title, WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, width, height, NULL, NULL, hinstance, NULL);
	if (!m_hwnd) {
		return FALSE;
	}

	m_width = width;
	m_height = height;

	// Store pointer to the self in the window data
	//
	::SetWindowLongPtr(m_hwnd, GWL_USERDATA, (LONG_PTR) this);

	// Show window
	//
	ShowWindow(m_hwnd, SW_SHOWNORMAL);
	UpdateWindow(m_hwnd);
	
	return true;
}


void Win32Application::DisposeBackbuffer()
{
	if (m_memoryDC) {
		::DeleteDC(m_memoryDC);
		m_memoryDC = 0;
	}

	if (m_memoryBmp) {
		::DeleteObject(m_memoryBmp);
		m_memoryBmp = 0;
	}
}


HDC Win32Application::GetBackbuffer()
{
	if (m_memoryDC)
		return m_memoryDC;

	RECT clientRect;
	::GetClientRect(m_hwnd, &clientRect);

	HDC hdc = ::GetDC(m_hwnd);

	m_width = clientRect.right;
	m_height = clientRect.bottom;

	m_memoryDC = ::CreateCompatibleDC(hdc);
	m_memoryBmp = ::CreateCompatibleBitmap(hdc, m_width, m_height);

	::SelectObject(m_memoryDC, m_memoryBmp);

	::ReleaseDC(m_hwnd, hdc);

	return m_memoryDC;
}


#pragma region Static WindowProc Trick!
LRESULT CALLBACK Win32Application::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	if (msg == WM_CREATE) {
		::SetForegroundWindow(hwnd);
		return 0;
	}

	Win32Application* app = (Win32Application*) ::GetWindowLongPtr(hwnd, GWL_USERDATA);
	BOOL handled = FALSE;
	if (app) {
		handled = app->OnMessage(msg, wparam, lparam);
	}

	if (!handled) {
		return (long) ::DefWindowProcA(hwnd, msg, wparam, lparam);
	}
	
	return 0;
}
#pragma endregion


#pragma region Virtual Member Functions
HRESULT Win32Application::OnCreate()
{
	return S_OK;
}


void Win32Application::OnRelease()
{
}


bool Win32Application::OnMessage(UINT msg, WPARAM wparam, LPARAM lparam)
{
	switch (msg) {
	case WM_CLOSE:
		::DestroyWindow(m_hwnd);
		m_hwnd = NULL;
		break;

	case WM_DESTROY:
		Release();
		::PostQuitMessage(0);
		break;

	case WM_GETMINMAXINFO:
		{
		MINMAXINFO* mmi = (MINMAXINFO*) lparam;
		mmi->ptMinTrackSize.x = 160;
		mmi->ptMinTrackSize.y = 100;
		}
		break;

	case WM_KEYDOWN:
		OnKeyDown((DWORD) wparam);
		break;

	case WM_CHAR:
		OnChar((char) wparam);
		break;

	case WM_SIZE:
	case WM_SIZING:
		DisposeBackbuffer();
		break;

	case WM_PAINT:
		{
		PAINTSTRUCT ps;
		HDC screenDC = ::BeginPaint(m_hwnd, &ps);
		HDC memoryDC = GetBackbuffer();	

		Graphics graphics(memoryDC);

		OnPaint(graphics);

		::BitBlt(screenDC, 0, 0, m_width, m_height, memoryDC, 0, 0, SRCCOPY);
		::EndPaint(m_hwnd, &ps);
		}
		break;

	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
		OnMouseDown(wparam, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
		break;

	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
		OnMouseUp(wparam, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
		break;

	case WM_MOUSEMOVE:
		OnMouseMove(wparam, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
		break;

	case WM_ERASEBKGND:
		return TRUE;
		break;
	}

	return FALSE;
}


void Win32Application::OnKeyDown(DWORD key)
{
}


void Win32Application::OnChar(char c)
{
}


void Win32Application::OnPaint(Graphics& graphics)
{
}


void Win32Application::OnMouseDown(DWORD button, int x, int y)
{
}


void Win32Application::OnMouseUp(DWORD button, int x, int y)
{
}


void Win32Application::OnMouseMove(DWORD button, int x, int y)
{
}
#pragma endregion